﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace CXW.Core
{
    public sealed class AddIn
    {
        Properties properties = new Properties();
        List<Runtime> runtimes = new List<Runtime>();

        internal string addInFileName = null;
        AddInManifest manifest = new AddInManifest();
        Dictionary<string, ExtensionPath> paths = new Dictionary<string, ExtensionPath>();
        bool enabled;

        static bool hasShownErrorMessage = false;
        /// <summary>根据类名称实例化该类</summary>
        /// <param name="className">类名称</param>
        /// <returns></returns>
        public object CreateObject(string className)
        {
            Type t = FindType(className);
            if (t != null)
                return Activator.CreateInstance(t);
            else
                return null;
        }
        /// <summary>查找指定类名的类是否存在</summary>
        /// <param name="className">类名称</param>
        /// <returns></returns>
        public Type FindType(string className)
        {
            LoadDependencies();
            foreach (Runtime runtime in runtimes)
            {
                Type t = runtime.FindType(className);
                if (t != null)
                {
                    return t;
                }
            }
            if (hasShownErrorMessage)
            {
                LoggingService.Error("Cannot find class: " + className);
            }
            else
            {
                hasShownErrorMessage = true;
                MessageService.ShowError("Cannot find class: " + className + "\nFuture missing objects will not cause an error message.");
            }
            return null;
        }

        volatile bool dependenciesLoaded;

        void LoadDependencies()
        {
            // Thread-safe dependency loading:
            // Because the methods being called should be thread-safe, there's
            // no problem when we load dependencies multiple times concurrently.
            // However, we need to make sure we don't return before the dependencies are ready,
            // so "bool dependenciesLoaded" must be volatile and set only at the very end of this method.
            if (!dependenciesLoaded)
            {
                LoggingService.Info("Loading addin " + this.Name);

                AssemblyLocator.Init();

                dependenciesLoaded = true;
            }
        }

        /// <remark/>
        public override string ToString()
        {
            return "[AddIn: " + Name + "]";
        }

        string customErrorMessage;

        /// <summary>
        /// Gets the message of a custom load error. Used only when AddInAction is set to CustomError.
        /// Settings this property to a non-null value causes Enabled to be set to false and
        /// Action to be set to AddInAction.CustomError.
        /// </summary>
        public string CustomErrorMessage
        {
            get { return customErrorMessage; }
            internal set
            {
                if (value != null)
                {
                    Enabled = false;
                }
                customErrorMessage = value;
            }
        }

        public List<Runtime> Runtimes
        {
            get { return runtimes; }
        }

        public string FileName
        {
            get { return addInFileName; }
            set { addInFileName = value; }
        }

        public string Name
        {
            get { return properties["name"]; }
        }

        public AddInManifest Manifest
        {
            get { return manifest; }
        }

        public Dictionary<string, ExtensionPath> Paths
        {
            get { return paths; }
        }

        public Properties Properties
        {
            get { return properties; }
        }

        public bool Enabled
        {
            get { return enabled; }
            set { enabled = value; }
        }

        internal AddIn()
        {
        }

        static void SetupAddIn(XmlReader reader, AddIn addIn, string hintPath)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && reader.IsStartElement())
                {
                    switch (reader.LocalName)
                    {
                        case "Runtime":
                            if (!reader.IsEmptyElement)
                            {
                                Runtime.ReadSection(reader, addIn, hintPath);
                            }
                            break;
                        case "Include":
                            if (reader.AttributeCount != 1)
                            {
                                throw new CoreException("Include requires ONE attribute.");
                            }
                            if (!reader.IsEmptyElement)
                            {
                                throw new CoreException("Include nodes must be empty!");
                            }
                            if (hintPath == null)
                            {
                                throw new CoreException("Cannot use include nodes when hintPath was not specified (e.g. when AddInManager reads a .addin file)!");
                            }
                            string fileName = Path.Combine(hintPath, reader.GetAttribute(0));
                            XmlReaderSettings xrs = new XmlReaderSettings();
                            xrs.NameTable = reader.NameTable; // share the name table
                            xrs.ConformanceLevel = ConformanceLevel.Fragment;
                            using (XmlReader includeReader = XmlTextReader.Create(fileName, xrs))
                            {
                                SetupAddIn(includeReader, addIn, Path.GetDirectoryName(fileName));
                            }
                            break;
                        case "Path":
                            if (reader.AttributeCount != 1)
                            {
                                throw new CoreException("Import node requires ONE attribute.");
                            }
                            string pathName = reader.GetAttribute(0);
                            ExtensionPath extensionPath = addIn.GetExtensionPath(pathName);
                            if (!reader.IsEmptyElement)
                            {
                                ExtensionPath.SetUp(extensionPath, reader, "Path");
                            }
                            break;
                        case "Manifest":
                            addIn.Manifest.ReadManifestSection(reader, hintPath);
                            break;
                        default:
                            throw new CoreException("Unknown root path node:" + reader.LocalName);
                    }
                }
            }
        }

        public ExtensionPath GetExtensionPath(string pathName)
        {
            if (!paths.ContainsKey(pathName))
            {
                return paths[pathName] = new ExtensionPath(pathName, this);
            }
            return paths[pathName];
        }

        public static AddIn Load(TextReader textReader, string hintPath = null, XmlNameTable nameTable = null)
        {
            if (nameTable == null)
                nameTable = new NameTable();
            try
            {
                AddIn addIn = new AddIn();
                using (XmlTextReader reader = new XmlTextReader(textReader, nameTable))
                {
                    while (reader.Read())
                    {
                        if (reader.IsStartElement())
                        {
                            switch (reader.LocalName)
                            {
                                case "AddIn":
                                    addIn.properties = Properties.ReadFromAttributes(reader);
                                    SetupAddIn(reader, addIn, hintPath);
                                    break;
                                default:
                                    throw new CoreException("Unknown add-in file.");
                            }
                        }
                    }
                }
                return addIn;
            }
            catch (XmlException ex)
            {
                throw new CoreException(ex.Message, ex);
            }
        }

        public static AddIn Load(string fileName, XmlNameTable nameTable = null)
        {
            try
            {
                using (TextReader textReader = File.OpenText(fileName))
                {
                    AddIn addIn = Load(textReader, Path.GetDirectoryName(fileName), nameTable);
                    addIn.addInFileName = fileName;
                    return addIn;
                }
            }
            catch (CoreException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new CoreException("Can't load " + fileName, e);
            }
        }
    }
}
